/*
 * Copyright 2010 WANdisco, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
package com.googlecode.svnj;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;

import javanet.staxutils.IndentingXMLStreamWriter;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.jackrabbit.util.Text;
import org.apache.jackrabbit.webdav.DavConstants;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.xml.DomUtil;
import org.apache.jackrabbit.webdav.xml.XmlSerializable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.googlecode.svnj.dav.SVNConstants;

public class XMLUtil {

    /** StAX methods */
    public static XMLStreamWriter createWriter(Writer w)
    throws XMLStreamException {
        XMLOutputFactory fac = XMLOutputFactory.newInstance();
        XMLStreamWriter ret = fac.createXMLStreamWriter(w);
        ret = new IndentingXMLStreamWriter(ret);
        return ret;
        
    }
    
    public static String xmlToString(XmlSerializable serializable, boolean pretty) {
        /* I feel pretty, oh so pretty! */
        if (serializable != null) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            try {
                Document doc = DomUtil.BUILDER_FACTORY.newDocumentBuilder()
                        .newDocument();
                doc.appendChild(serializable.toXml(doc));

                Result result = null;
                /*ContentHandler handler = SerializingContentHandler
                        .getSerializer(out);
                result = new SAXResult(handler);
                */
                result = new StreamResult(out);
                TransformerFactory factory = TransformerFactory.newInstance();
                Transformer transformer = factory.newTransformer();
                transformer.setOutputProperty(OutputKeys.INDENT, pretty ? "yes" : "no" );
                transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
                transformer.transform(new DOMSource(doc), result);
                  

                return out.toString();
            } catch (RuntimeException re) {
                throw re;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return null;
    }
    /*
    wire format of error messages:
    <?xml version="1.0" encoding="utf-8"?>
    <D:error xmlns:D="DAV:" xmlns:m="http://apache.org/dav/xmlns" xmlns:C="svn:">
    <C:error/>
    <m:human-readable errcode="165001">
    Commit blocked by pre-commit hook (exit code 1) with output:
    you cannot checkin, sorry dude

    </m:human-readable>
    </D:error>
         */
        
    private static class Exception2XML implements XmlSerializable {
        Throwable t;
        private Exception2XML(Throwable t) {
            this.t = t;
        }
        public Element toXml(Document doc) {
            Element ret = DomUtil.createElement(doc, DavException.XML_ERROR, DavConstants.NAMESPACE);
            DomUtil.addChildElement(ret, DavException.XML_ERROR, SVNConstants.NS_SVN); // FIXME: right NS for this?
            // human readable
            Element hr = DomUtil.addChildElement(ret, "human-readable", SVNConstants.NS_APACHE_DAV);
            hr.setTextContent(Text.encodeIllegalXMLCharacters(t.toString()));
            return ret;
        }
    }
    public static XmlSerializable encodeThrowable(Throwable t) {
        return new Exception2XML(t);
    }
    
    public static Document newDocument() {
        try {
            return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        } catch (ParserConfigurationException pce) {
            throw new RuntimeException(pce);
        }
    }
    public static XMLStreamWriter initWriter(OutputStream os, String startElem)
    throws XMLStreamException {
        XMLOutputFactory fac = XMLOutputFactory.newInstance();
        XMLStreamWriter w = fac.createXMLStreamWriter(os);
        w = new IndentingXMLStreamWriter(w);
        initialize(w, startElem);
        return w;
    }
    public static XMLStreamReader initReader(Reader rdr) throws XMLStreamException {
        XMLInputFactory fac = XMLInputFactory.newInstance();
        fac.setProperty("javax.xml.stream.isCoalescing", Boolean.TRUE);
        return fac.createXMLStreamReader(rdr);
    }
    public static void initialize(XMLStreamWriter w, String startElement)
    throws XMLStreamException {
        w.writeStartDocument("1.0");
        w.writeStartElement(startElement);
        w.writeNamespace("S", "svn:");
        w.writeNamespace("V", "http://subversion.tigris.org/xmlns/dav/");
        w.writeNamespace("D", "DAV:");
    }
    
    /** DOM parsing methods */
    public static Document parseDoc(Reader rdr) throws IOException {
        Document doc = null;
        InputSource src = new InputSource(rdr);
        try {
            DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance();
            /* Without 'namespace-aware', local names of nodes
             * are null, and relying on the namespace prefix
             * (e.g., "S:") is brittle and goofy
             */
            fac.setNamespaceAware(true);
            doc = fac.newDocumentBuilder().parse(src);
            return doc;
        } catch (ParserConfigurationException pce){
            throw new RuntimeException(pce);
        } catch (SAXException sxe){
            throw new RuntimeException(sxe);
        }
    }
    public static Document parseDoc(InputStream is) throws IOException {
        return parseDoc(new InputStreamReader(is));
    }
    
    public static Node parseNode(Reader r) throws IOException {
        Document doc = parseDoc(r);
        return doc.getFirstChild();
    }
    
    public static Node parseNode(InputStream is) throws IOException {
        return parseNode(new InputStreamReader(is));
    }
    
    public static boolean hasChild(Node parent, String elemName) {
        return findChild(parent, elemName) != null;
    }
    
    public static Node findChild(Node parent, String elemName) {
        Node ret = null;
        NodeList nl = parent.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node n = nl.item(i);
            if (elemName.equals(n.getLocalName())) {
                ret = n;
                break;
            } else if (elemName.equals(n.getNodeName())) {
                throw new RuntimeException(
                        "Do not use fully qualified name: '" + elemName + "'"
                        );
                //ret = n;
                //break;
            }
        }
        return ret;
    }
    
    public static Long getChildLong(Node parent, String elemName, Long defalt) {
        Long ret = defalt;
        String s = getChildText(parent, elemName);
        if (s != null) {
            try {
                ret = Long.parseLong(s);
            } catch (NumberFormatException ignore) {}
        }
        return ret;
        
    }
    public static String getChildText(Node parent, String elemName) {
        String ret = null;
        Node n = findChild(parent, elemName);
        if (n != null) {
            ret = n.getTextContent();
        }
        return ret;
    }
    public static String getAttribute(Node n, String attName) {
        String ret = null;
        if (n.getAttributes() != null) {
            Node att = n.getAttributes().getNamedItem(attName);
            if (att != null) {
                ret = att.getNodeValue();
            }
        }
        return ret;
    }
    
    public static Long getAttributeLong(Node n, String attName) {
        Long ret = null;
        String s = getAttribute(n, attName);
        if (s != null) {
            try {
                ret = Long.parseLong(s);
            } catch (NumberFormatException ignore) { }
        }
        return ret;
    }
}
